For the Final Project, we have chosen Real/Fake Job Posting Prediction as our data set from Kaggle.

About Dataset:

This dataset contains 18K job descriptions out of which about 800 are fake. The data consists of both textual information and meta-information about the jobs. The dataset can be used to create classification models which can learn the job descriptions which are fraudulent.

# Let's read our data
data = read.csv("C:/Users/kriti/Downloads/fake_job_postings.csv", header = TRUE, na.strings = c("","NA"))
head(data)
table(data$fraudulent)

    0     1 
17014   866 
#taking  0 : 2000 , 1 : 866
sample_0 <- data[data$fraudulent == 0, ]
set.seed(123)
sample_0 <- sample_0[sample(nrow(sample_0), 2000), ]
sample_1 <- data[data$fraudulent == 1, ]
data <- rbind(sample_0, sample_1)
table(data$fraudulent)

   0    1 
2000  866 
data = data[sample(nrow(data)), ]
index <- createDataPartition(y = data$fraudulent , p=0.9 , list = FALSE)
train_data <- data[index , ]
test_data <- data[-index , ]

Our target variable is to find which job description are fraudulent or real. Let’s check if it’s imbalanced or not

#train_data
count = table(train_data$fraudulent)
# Using Bar plot
barplot(count, 
        main = "Count of Fraudulent vs. Non-Fraudulent (Train Data)",
        xlab = "Fraudulent",
        ylab = "Count",
        col = c("lightblue", "pink"), 
        legend = c("Not Fraud", "Fraud"))


#test_data
count = table(test_data$fraudulent)
# Using Bar plot
barplot(count, 
        main = "Count of Fraudulent vs. Non-Fraudulent (Test Data)",
        xlab = "Fraudulent",
        ylab = "Count",
        col = c("lightblue", "pink"), 
        legend = c("Not Fraud", "Fraud"))

Here we can see that it’s highly imbalanced.

Our steps to proceed:

  1. Learning the data
  2. Data Cleaning ( handling Missing values, deleting not-required features )
  3. Visualization Plots, graphs to understand variables
  4. Feature Engineering ( Creating new features or transforming existing ones )
  5. Data Pre-processing ( Tokenization, lemmatization, Text-Vectorization, train-test split )
  6. Model Selection and Training
  7. Model Evaluation
  8. Model Fine-tuning, final model selection
  9. Model Deployment

Let’s start with learning about the dataset:

summary(train_data)
     job_id         title             location          department        salary_range      
 Min.   :    1   Length:2580        Length:2580        Length:2580        Length:2580       
 1st Qu.: 4812   Class :character   Class :character   Class :character   Class :character  
 Median : 9108   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   : 9502                                                                              
 3rd Qu.:14596                                                                              
 Max.   :17876                                                                              
 company_profile    description        requirements         benefits         telecommuting    
 Length:2580        Length:2580        Length:2580        Length:2580        Min.   :0.00000  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:0.00000  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :0.00000  
                                                                             Mean   :0.05194  
                                                                             3rd Qu.:0.00000  
                                                                             Max.   :1.00000  
 has_company_logo has_questions    employment_type    required_experience required_education
 Min.   :0.0000   Min.   :0.0000   Length:2580        Length:2580         Length:2580       
 1st Qu.:0.0000   1st Qu.:0.0000   Class :character   Class :character    Class :character  
 Median :1.0000   Median :0.0000   Mode  :character   Mode  :character    Mode  :character  
 Mean   :0.6709   Mean   :0.4391                                                            
 3rd Qu.:1.0000   3rd Qu.:1.0000                                                            
 Max.   :1.0000   Max.   :1.0000                                                            
   industry          function.           fraudulent    
 Length:2580        Length:2580        Min.   :0.0000  
 Class :character   Class :character   1st Qu.:0.0000  
 Mode  :character   Mode  :character   Median :0.0000  
                                       Mean   :0.2996  
                                       3rd Qu.:1.0000  
                                       Max.   :1.0000  
summary(test_data)
     job_id         title             location          department        salary_range      
 Min.   :   27   Length:286         Length:286         Length:286         Length:286        
 1st Qu.: 4994   Class :character   Class :character   Class :character   Class :character  
 Median : 8951   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   : 9348                                                                              
 3rd Qu.:14298                                                                              
 Max.   :17823                                                                              
 company_profile    description        requirements         benefits         telecommuting    
 Length:286         Length:286         Length:286         Length:286         Min.   :0.00000  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:0.00000  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :0.00000  
                                                                             Mean   :0.02797  
                                                                             3rd Qu.:0.00000  
                                                                             Max.   :1.00000  
 has_company_logo has_questions   employment_type    required_experience required_education
 Min.   :0.0000   Min.   :0.000   Length:286         Length:286          Length:286        
 1st Qu.:0.0000   1st Qu.:0.000   Class :character   Class :character    Class :character  
 Median :1.0000   Median :0.000   Mode  :character   Mode  :character    Mode  :character  
 Mean   :0.6434   Mean   :0.472                                                            
 3rd Qu.:1.0000   3rd Qu.:1.000                                                            
 Max.   :1.0000   Max.   :1.000                                                            
   industry          function.           fraudulent    
 Length:286         Length:286         Min.   :0.0000  
 Class :character   Class :character   1st Qu.:0.0000  
 Mode  :character   Mode  :character   Median :0.0000  
                                       Mean   :0.3252  
                                       3rd Qu.:1.0000  
                                       Max.   :1.0000  

Categorical variables are:

  1. title
  2. location
  3. department
  4. salary_range
  5. company_profile
  6. description
  7. requirements
  8. benefits
  9. employment_type
  10. required_experience
  11. required_education
  12. industry
  13. function

Numerical variables are:

  1. job_id
  2. telecommuting
  3. has_company_logo
  4. has_questions

The last variable - fraudulent is a numerical binary variable to be predicted.

# Let's check for null values
colSums(is.na(data))
             job_id               title            location          department        salary_range 
                  0                   0                  54                1804                2337 
    company_profile         description        requirements            benefits       telecommuting 
                907                   0                 457                1186                   0 
   has_company_logo       has_questions     employment_type required_experience  required_education 
                  0                   0                 619                1237                1377 
           industry           function.          fraudulent 
                824                1081                   0 

Here, We can see that many columns have null values, let’s handle them:

# Calculating the correlation for: department, salary_range,function.
##department
#Train Data
train_data$department = as.numeric(as.factor(train_data$department))
train_data$department = ifelse(is.na(train_data$department), 0, train_data$department)
correlation_matrix = cor(train_data$department, train_data$fraudulent)
correlation_matrix # -0.07768365
[1] -0.07354623
#Test_data
test_data$department = as.numeric(as.factor(test_data$department))
test_data$department = ifelse(is.na(test_data$department), 0, test_data$department)
correlation_matrix = cor(test_data$department, test_data$fraudulent)
correlation_matrix #-0.02963282
[1] -0.0786168
##salary Range
#Train Data
train_data$salary_range = as.numeric(as.factor(train_data$salary_range))
train_data$salary_range = ifelse(is.na(train_data$salary_range), 0, train_data$salary_range)
correlation_matrix = cor(train_data$salary_range, train_data$fraudulent)
correlation_matrix #0.1017228
[1] 0.101208
#Test Data
test_data$salary_range = as.numeric(as.factor(test_data$salary_range))
test_data$salary_range = ifelse(is.na(test_data$salary_range), 0, test_data$salary_range)
correlation_matrix = cor(test_data$salary_range, test_data$fraudulent)
correlation_matrix #0.124986
[1] 0.1258764
##function. 
#Train_data
train_data$function. = as.numeric(as.factor(train_data$function.))
train_data$function. = ifelse(is.na(train_data$function.), 0, train_data$function.)
correlation_matrix = cor(train_data$function., train_data$fraudulent)
correlation_matrix #-0.1476297
[1] -0.1522213
#Test Data
test_data$function. = as.numeric(as.factor(test_data$function.))
test_data$function. = ifelse(is.na(test_data$function.), 0, test_data$function.)
correlation_matrix = cor(test_data$function., test_data$fraudulent)
correlation_matrix #-0.180516
[1] -0.1438862
# The correlation value is very far away from the range -1 to 1, therefore it shows weak relation and we'll remove these variables from our dataset.
train_data = subset(train_data, select = -c(salary_range,department, function.))
test_data = subset(test_data, select = -c(salary_range,department, function.))

Let’s impute the missing values:

No numerical variables have null values.

Let’s handle the missing values by filling the NA’s with an empty strings as this is considered as the best way until now because we would be combining these variables anyhow into one variable and then work upon them.


data = data.frame(
  lapply(train_data, function(x) ifelse(is.na(x), '', x))
)
colSums(is.na(train_data))
             job_id               title            location     company_profile         description 
                  0                   0                  43                 818                   0 
       requirements            benefits       telecommuting    has_company_logo       has_questions 
                409                1066                   0                   0                   0 
    employment_type required_experience  required_education            industry          fraudulent 
                570                1115                1249                 747                   0 

Let’s understand our numerical variables:

  1. job_id
  2. telecommuting
  3. has_company_logo
  4. has_questions

count = table(train_data$telecommuting)
barplot(count,
        main = "Count Plot for telecommuting",
        xlab = "id",
        ylab = "Count",
        col = "skyblue",
        border = "black"
)


count = table(train_data$has_company_logo)
barplot(count,
        main = "Count Plot for company_logo",
        xlab = "id",
        ylab = "Count",
        col = "skyblue",
        border = "black"
)


count = table(train_data$has_questions)
barplot(count,
        main = "Count Plot for questions",
        xlab = "id",
        ylab = "Count",
        col = "skyblue",
        border = "black"
)

There is high imbalance of data for telecommuting variable.

let’s check their correlation with the target variable:

# For job_id
correlation <- cor(train_data$job_id, train_data$fraudulent)
correlation #0.1557648
[1] 0.1482325
# For telecommuting
correlation <- cor(train_data$telecommuting, train_data$fraudulent)
correlation #0.06998303
[1] 0.07569604
# For has_company_logo
correlation <- cor(train_data$has_company_logo, train_data$fraudulent)
correlation #-0.4789252
[1] -0.4819276
# For has_questions
correlation <- cor(train_data$has_questions, train_data$fraudulent)
correlation #-0.2056319
[1] -0.205375
# Removing these three variable from the data set
train_data = subset(train_data, select = -c(job_id,telecommuting,has_questions))
test_data = subset(test_data, select = -c(job_id,telecommuting,has_questions))

Job_id correlation value indicates a weak relationship and hence, we can remove this because even if we critical think, job_id doesn’t help us to understand if the job is fraudulent or not. It justs act as a unique identifier in terms of database term.

Talking about telecommuting, has_questions variable we should remove them as well. I think it’s better to keep has_company_logo variable for now as it atleast gives us a number close to -1.

# Let's create a function to perform the chi-square test
chi_test = function(var, target) {
  chi_sq = chisq.test(var, target)
  return(chi_sq)
}
cat_var = c(
    "title",
    "location",
    "company_profile",
    "description",
    "requirements",
    "benefits",
    "employment_type",
    "required_experience",
    "required_education",
    "industry"
)
# Perform chi-square tests for each variable and print the results
for (var in cat_var) {
  chi_sq = chi_test(train_data[[var]], train_data$fraudulent)
  var
  print(chi_sq)
}
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 2466.1, df = 1960, p-value = 3.524e-14
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 1741.2, df = 975, p-value < 2.2e-16
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 1762, df = 660, p-value < 2.2e-16
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 2580, df = 2277, p-value = 7.988e-06
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 2160.3, df = 1812, p-value = 2.343e-08
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 1506, df = 1141, p-value = 1.643e-12


    Pearson's Chi-squared test

data:  var and target
X-squared = 37.836, df = 4, p-value = 1.211e-07


    Pearson's Chi-squared test

data:  var and target
X-squared = 59.935, df = 6, p-value = 4.639e-11
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 158.28, df = 11, p-value < 2.2e-16
Warning: Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  var and target
X-squared = 660.72, df = 106, p-value < 2.2e-16

We can see that the p-values are < 0.05, therefore we’ll accept that these are significant variables.

char_vars = sapply(train_data, is.character)
# Convert those variables to factor
#data[char_vars] = lapply(data[char_vars], as.factor)

Let’s understand a few more variables through plotting the graphs for them:

#train_data
# Plotting the count for required_experience in the data:
experience_counts = table(train_data$required_experience)
top_experience_index = head(order(experience_counts, decreasing = TRUE), 10)
top_counts = experience_counts[top_experience_index]
barplot(top_counts,
        main = "Count Plot for Experience",
        xlab = "Experience",
        ylab = "Count",
        col = "skyblue",
        border = "black",
        las = 2  
)

We can deduce that most values are null and then the hiring is happening more for senior level and entry level as compared to other types of experience needed.

# Let's extract country from the location and check it's count
train_data$country <- sub(",.*", "", train_data$location)
test_data$country <- sub(",.*", "", test_data$location)

# Plotting the count for 10 highest counts countries from the data:
country_counts = table(train_data$country)
top_countries_index = head(order(country_counts, decreasing = TRUE), 10)
top_counts = country_counts[top_countries_index]
barplot(top_counts,
        main = "Count Plot for Countries",
        xlab = "Countries",
        ylab = "Count",
        col = "skyblue",
        border = "black",
        las = 2  
)

#train_data$country = as.factor(train_data$country)

US has the most openings.

# Plotting the count for highest counts education from the data:
ed_counts = table(train_data$required_education)
ed_index = head(order(ed_counts, decreasing = TRUE), 5)
top_counts = ed_counts[ed_index]
barplot(top_counts,
        main = "Count Plot for Education Level",
        xlab = "Education",
        ylab = "Count",
        col = "skyblue",
        border = "black",
        las = 2  
)

Again for this, Bachelor’s degree requirement is more.

Let’s try to find the most important features among these text variables of our dataset, to combine them

chi_test <- function(var, target) {
  chi_sq <- chisq.test(var, target)
  return(chi_sq$statistic)
}

cat_var <- c(
  "title",
  "country",
  "company_profile",
  "description",
  "requirements",
  "benefits",
  "employment_type",
  "required_experience",
  "required_education",
  "industry",
  "has_company_logo"
)

# Initializing an empty vector to store chi-square statistics for each variable
chi_sq_stats <- numeric(length(cat_var))

# Performing chi-square tests for each variable and storing the results
for (i in seq_along(cat_var)) {
  chi_sq_stats[i] <- chi_test(train_data[[cat_var[i]]], train_data$fraudulent)
}
Warning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrectWarning: Chi-squared approximation may be incorrect
# New data frame with variable names and their corresponding chi-square statistics created
chi_sq_df <- data.frame(variable = cat_var, chi_sq_statistic = chi_sq_stats)

# Ordering based on chi-square statistics in descending order
chi_sq_df <- chi_sq_df[order(chi_sq_df$chi_sq_statistic, decreasing = TRUE), ]
chi_sq_df

We can discard country, location, required_education ,required_experience ,employment_type, location from the dataset.

#train_data
train_data = subset(train_data, select = -c(country, location, required_education ,required_experience ,employment_type, location,has_company_logo ))
train_data
#test_data
#train_data
test_data = subset(test_data, select = -c(country, location, required_education ,required_experience ,employment_type, location,has_company_logo ))
test_data

Now, let’s combine all our variables: title, company_profile, description, requirements, benefits, industry:

train_data$text <- paste(train_data$title, train_data$company_profile, train_data$description, train_data$requirements, train_data$benefits, train_data$industry, sep = " ")

test_data$text <- paste(test_data$title, test_data$company_profile, test_data$description, test_data$requirements, test_data$benefits, test_data$industry, sep = " ")

# Removing these variables:
#train_data
train_data = subset(train_data, select = -c(title, company_profile, description, requirements,benefits,industry))

#test_data
test_data = subset(test_data, select = -c(title, company_profile, description, requirements,benefits,industry))

Randomizing the order:

set.seed(123)
train_data = train_data[sample(nrow(train_data)), ]
test_data = test_data[sample(nrow(test_data)), ]

Let’s perform some pre-processing

library(tm)
library(wordcloud)
library(SnowballC)
text_corpus_train = VCorpus(VectorSource(train_data$text))
text_corpus_test = VCorpus(VectorSource(test_data$text))

Cleaning the corpus

#train_data
text_corpus_clean_train <- tm_map(text_corpus_train, content_transformer(tolower)) # Convert to lowercase
text_corpus_clean_train <- tm_map(text_corpus_clean_train, removeNumbers) # Remove numbers
text_corpus_clean_train <- tm_map(text_corpus_clean_train,removeWords, stopwords()) # Remove stop words
text_corpus_clean_train <- tm_map(text_corpus_clean_train, removePunctuation) # Remove punctuation
text_corpus_clean_train <- tm_map(text_corpus_clean_train, stemDocument) # Stemming
text_corpus_clean_train <- tm_map(text_corpus_clean_train, stripWhitespace) # Remove extra whitespaces

#test_data
text_corpus_clean_test <- tm_map(text_corpus_test, content_transformer(tolower)) # Convert to lowercase
text_corpus_clean_test <- tm_map(text_corpus_clean_test, removeNumbers) # Remove numbers
text_corpus_clean_test <- tm_map(text_corpus_clean_test,removeWords, stopwords()) # Remove stop words
text_corpus_clean_test <- tm_map(text_corpus_clean_test, removePunctuation) # Remove punctuation
text_corpus_clean_test <- tm_map(text_corpus_clean_test, stemDocument) # Stemming
text_corpus_clean_test <- tm_map(text_corpus_clean_test, stripWhitespace) # Remove extra whitespaces

Creating Wordclouds:

#train_data
wordcloud(text_corpus_clean_train,min.freq = 50, random.order = FALSE)

fraudulent = subset(text_corpus_clean_train, train_data$fraudulent == 1)
non_fraudulent = subset(text_corpus_clean_train, train_data$fraudulent == 0)
wordcloud(fraudulent, max.words = 20, scale = c(3, 0.5))

wordcloud(non_fraudulent, max.words = 20, scale = c(3, 0.5))


#test_data
wordcloud(text_corpus_clean_test,min.freq = 50, random.order = FALSE)

fraudulent = subset(text_corpus_clean_test, test_data$fraudulent == 1)
non_fraudulent = subset(text_corpus_clean_train, test_data$fraudulent == 0)
wordcloud(fraudulent, max.words = 20, scale = c(3, 0.5))

wordcloud(non_fraudulent, max.words = 20, scale = c(3, 0.5))

Creating the document term matrix:

#train_data
train_data_dtm = DocumentTermMatrix(text_corpus_clean_train)
train_data_dtm 
<<DocumentTermMatrix (documents: 2580, terms: 42872)>>
Non-/sparse entries: 389772/110219988
Sparsity           : 100%
Maximal term length: 517
Weighting          : term frequency (tf)
#test_data
test_data_dtm = DocumentTermMatrix(text_corpus_clean_test)
test_data_dtm 
<<DocumentTermMatrix (documents: 286, terms: 8819)>>
Non-/sparse entries: 41206/2481028
Sparsity           : 98%
Maximal term length: 630
Weighting          : term frequency (tf)

Let’s perform train-test split, we’ll be taking 70% data for training and 30% for testing.

index = round(nrow(train_data_dtm)*0.70)
train_dtm = train_data_dtm[1:index,]
test_dtm = train_data_dtm[(index+1):nrow(train_data_dtm),]

train_label = train_data[1:index,]$fraudulent
test_label = train_data[(index+1):nrow(train_data),]$fraudulent

#checking the length. 
nrow(train_dtm)
[1] 1806
length(train_label)
[1] 1806
nrow(test_dtm)
[1] 774
length(test_label)
[1] 774
#simple benchmark model

#sum(test_data$fraudulent == 1)
length(test_data$fraudulent)
[1] 286
vector <- rep(1 , 286)
pred_table <- table(vector , test_data$fraudulent)
pred_table 
      
vector   0   1
     1 193  93
total <- 193 + 93
TP<-0
TN<-193  
FP<-0
FN<-93
accuracy <- (TP + TN) / total
accuracy #0.6748252
[1] 0.6748252

Creating frequency terms using our document term matrix data

#train_data
textFreqWords_train = findFreqTerms(train_dtm,10)
train_dtm_freq = train_dtm[,textFreqWords_train]
train_dtm_freq
<<DocumentTermMatrix (documents: 1806, terms: 3217)>>
Non-/sparse entries: 224755/5585147
Sparsity           : 96%
Maximal term length: 252
Weighting          : term frequency (tf)
#Test_data
textFreqWords_test = findFreqTerms(test_dtm,10)
test_dtm_freq = test_dtm[,textFreqWords_test]
test_dtm_freq
<<DocumentTermMatrix (documents: 774, terms: 2006)>>
Non-/sparse entries: 92067/1460577
Sparsity           : 94%
Maximal term length: 65
Weighting          : term frequency (tf)
convertCounts = function(x) {
  x = ifelse(x > 0, "Yes", "No")
}

train_text = apply(train_dtm_freq, MARGIN = 2, convertCounts)
test_text = apply(test_dtm_freq, MARGIN = 2, convertCounts)

The Models we’ll be using here are:

1. Naive Bayes

2. Logistic Regression ( taking too long )

4. Decision Trees

5. Gradient Boosting Machine

6. Random Forest

7. RNN ( LSTM )

Let’s start with Naive Bayes:

#calculations for inverse of confusion matrix to find out the fraud cases. 
confusion_matrix
$t
   y
x     0   1
  0 462  27
  1  75 210

$prop.row
   y
x            0          1
  0 0.94478528 0.05521472
  1 0.26315789 0.73684211

$prop.col
   y
x           0         1
  0 0.8603352 0.1139241
  1 0.1396648 0.8860759

$prop.tbl
   y
x            0          1
  0 0.59689922 0.03488372
  1 0.09689922 0.27131783
total <- 210+462+75+27
TP<-210
TN<-462
FP<-75
FN<-27

accuracy <- (TP + TN) / total
accuracy
[1] 0.8682171
recall <- TP/(TP+FN)
recall
[1] 0.8860759
precision<- TP/(TP+FP)
precision
[1] 0.7368421
F1_score <- 2*((precision*recall)/(precision+recall))
F1_score
[1] 0.8045977
str(train_label)
 int [1:1806] 0 0 0 0 1 0 0 0 0 1 ...

Decision Trees

Gradient Boosting Machine

set.seed(1)
library(caret)
library(gbm)
train_control = trainControl(method = "cv", number = 5)
gbm_model <- train(as.formula(textTrainLabels ~ .,), 
                   data = textTrain_df, 
                   method = "gbm",
                   trControl = train_control,
                   tuneLength = 5)  # Number of models to evaluate

# Print the trained model
print(gbm_model)

# predicting our model
gbm_prediction = predict(gbm_model, textTest_df)

# Creating a confusion matrix
table(gbm_prediction, textTest_df)

Random Forest

set.seed(1)
library(caret)
library(gbm)
train_control = trainControl(method = "cv", number = 3)
rf_model <- train(as.formula(textTrainLabels ~ .,), 
                  data = textTrain_df, 
                  method = "rf",
                  trControl = train_control,
                  tuneLength = 3,  
                  importance = TRUE)

print(rf_model)

# predicting our model
random_forest_prediction = predict(rf_model, textTest_df)

# Creating a confusion matrix
table(random_forest_prediction, textTest_df)
# handle imbalance dataset
# Add benchmark model
LS0tDQp0aXRsZTogIk1MIEZpbmFsIFByb2plY3QiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEZvciB0aGUgRmluYWwgUHJvamVjdCwgd2UgaGF2ZSBjaG9zZW4gUmVhbC9GYWtlIEpvYiBQb3N0aW5nIFByZWRpY3Rpb24gYXMgb3VyIGRhdGEgc2V0IGZyb20gS2FnZ2xlLg0KDQojIEhlcmUncyB0aGUgbGluayBvZiBvdXIgZGF0YXNldDogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9zaGl2YW1iL3JlYWwtb3ItZmFrZS1mYWtlLWpvYnBvc3RpbmctcHJlZGljdGlvbg0KDQojIEFib3V0IERhdGFzZXQ6DQojIFRoaXMgZGF0YXNldCBjb250YWlucyAxOEsgam9iIGRlc2NyaXB0aW9ucyBvdXQgb2Ygd2hpY2ggYWJvdXQgODAwIGFyZSBmYWtlLiBUaGUgZGF0YSBjb25zaXN0cyBvZiBib3RoIHRleHR1YWwgaW5mb3JtYXRpb24gYW5kIG1ldGEtaW5mb3JtYXRpb24gYWJvdXQgdGhlIGpvYnMuIFRoZSBkYXRhc2V0IGNhbiBiZSB1c2VkIHRvIGNyZWF0ZSBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgd2hpY2ggY2FuIGxlYXJuIHRoZSBqb2IgZGVzY3JpcHRpb25zIHdoaWNoIGFyZSBmcmF1ZHVsZW50Lg0KDQpgYGB7cn0NCiMgTGV0J3MgcmVhZCBvdXIgZGF0YQ0KZGF0YSA9IHJlYWQuY3N2KCJDOi9Vc2Vycy9rcml0aS9Eb3dubG9hZHMvZmFrZV9qb2JfcG9zdGluZ3MuY3N2IiwgaGVhZGVyID0gVFJVRSwgbmEuc3RyaW5ncyA9IGMoIiIsIk5BIikpDQpoZWFkKGRhdGEpDQpgYGANCg0KDQpgYGB7cn0NCnRhYmxlKGRhdGEkZnJhdWR1bGVudCkNCiN0YWtpbmcgIDAgOiAyMDAwICwgMSA6IDg2Ng0Kc2FtcGxlXzAgPC0gZGF0YVtkYXRhJGZyYXVkdWxlbnQgPT0gMCwgXQ0Kc2V0LnNlZWQoMTIzKQ0Kc2FtcGxlXzAgPC0gc2FtcGxlXzBbc2FtcGxlKG5yb3coc2FtcGxlXzApLCAyMDAwKSwgXQ0Kc2FtcGxlXzEgPC0gZGF0YVtkYXRhJGZyYXVkdWxlbnQgPT0gMSwgXQ0KZGF0YSA8LSByYmluZChzYW1wbGVfMCwgc2FtcGxlXzEpDQp0YWJsZShkYXRhJGZyYXVkdWxlbnQpDQpgYGANCmBgYHtyfQ0KZGF0YSA9IGRhdGFbc2FtcGxlKG5yb3coZGF0YSkpLCBdDQpgYGANCg0KYGBge3J9DQppbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkYXRhJGZyYXVkdWxlbnQgLCBwPTAuOSAsIGxpc3QgPSBGQUxTRSkNCnRyYWluX2RhdGEgPC0gZGF0YVtpbmRleCAsIF0NCnRlc3RfZGF0YSA8LSBkYXRhWy1pbmRleCAsIF0NCmBgYA0KIyBPdXIgdGFyZ2V0IHZhcmlhYmxlIGlzIHRvIGZpbmQgd2hpY2ggam9iIGRlc2NyaXB0aW9uIGFyZSBmcmF1ZHVsZW50IG9yIHJlYWwuIExldCdzIGNoZWNrIGlmIGl0J3MgaW1iYWxhbmNlZCBvciBub3QNCg0KYGBge3J9DQojdHJhaW5fZGF0YQ0KY291bnQgPSB0YWJsZSh0cmFpbl9kYXRhJGZyYXVkdWxlbnQpDQojIFVzaW5nIEJhciBwbG90DQpiYXJwbG90KGNvdW50LCANCiAgICAgICAgbWFpbiA9ICJDb3VudCBvZiBGcmF1ZHVsZW50IHZzLiBOb24tRnJhdWR1bGVudCAoVHJhaW4gRGF0YSkiLA0KICAgICAgICB4bGFiID0gIkZyYXVkdWxlbnQiLA0KICAgICAgICB5bGFiID0gIkNvdW50IiwNCiAgICAgICAgY29sID0gYygibGlnaHRibHVlIiwgInBpbmsiKSwgDQogICAgICAgIGxlZ2VuZCA9IGMoIk5vdCBGcmF1ZCIsICJGcmF1ZCIpKQ0KDQojdGVzdF9kYXRhDQpjb3VudCA9IHRhYmxlKHRlc3RfZGF0YSRmcmF1ZHVsZW50KQ0KIyBVc2luZyBCYXIgcGxvdA0KYmFycGxvdChjb3VudCwgDQogICAgICAgIG1haW4gPSAiQ291bnQgb2YgRnJhdWR1bGVudCB2cy4gTm9uLUZyYXVkdWxlbnQgKFRlc3QgRGF0YSkiLA0KICAgICAgICB4bGFiID0gIkZyYXVkdWxlbnQiLA0KICAgICAgICB5bGFiID0gIkNvdW50IiwNCiAgICAgICAgY29sID0gYygibGlnaHRibHVlIiwgInBpbmsiKSwgDQogICAgICAgIGxlZ2VuZCA9IGMoIk5vdCBGcmF1ZCIsICJGcmF1ZCIpKQ0KYGBgDQojIEhlcmUgd2UgY2FuIHNlZSB0aGF0IGl0J3MgaGlnaGx5IGltYmFsYW5jZWQuDQoNCiMgT3VyIHN0ZXBzIHRvIHByb2NlZWQ6DQoxLiBMZWFybmluZyB0aGUgZGF0YQ0KMi4gRGF0YSBDbGVhbmluZyAoIGhhbmRsaW5nIE1pc3NpbmcgdmFsdWVzLCBkZWxldGluZyBub3QtcmVxdWlyZWQgZmVhdHVyZXMgKQ0KMy4gVmlzdWFsaXphdGlvbiBQbG90cywgZ3JhcGhzIHRvIHVuZGVyc3RhbmQgdmFyaWFibGVzDQo0LiBGZWF0dXJlIEVuZ2luZWVyaW5nICggQ3JlYXRpbmcgbmV3IGZlYXR1cmVzIG9yIHRyYW5zZm9ybWluZyBleGlzdGluZyBvbmVzICkNCjYuIERhdGEgUHJlLXByb2Nlc3NpbmcgKCBUb2tlbml6YXRpb24sIGxlbW1hdGl6YXRpb24sIFRleHQtVmVjdG9yaXphdGlvbiwgdHJhaW4tdGVzdCBzcGxpdCApDQo3LiBNb2RlbCBTZWxlY3Rpb24gYW5kIFRyYWluaW5nDQo4LiBNb2RlbCBFdmFsdWF0aW9uDQo5LiBNb2RlbCBGaW5lLXR1bmluZywgZmluYWwgbW9kZWwgc2VsZWN0aW9uDQoxMC4gTW9kZWwgRGVwbG95bWVudA0KDQoNCiMgTGV0J3Mgc3RhcnQgd2l0aCBsZWFybmluZyBhYm91dCB0aGUgZGF0YXNldDoNCg0KYGBge3J9DQpzdW1tYXJ5KHRyYWluX2RhdGEpDQpzdW1tYXJ5KHRlc3RfZGF0YSkNCmBgYA0KIyBDYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlOg0KMS4gdGl0bGUNCjIuIGxvY2F0aW9uDQozLiBkZXBhcnRtZW50DQo0LiBzYWxhcnlfcmFuZ2UNCjUuIGNvbXBhbnlfcHJvZmlsZQ0KNi4gZGVzY3JpcHRpb24NCjcuIHJlcXVpcmVtZW50cw0KOC4gYmVuZWZpdHMNCjkuIGVtcGxveW1lbnRfdHlwZQ0KMTAuIHJlcXVpcmVkX2V4cGVyaWVuY2UNCjExLiByZXF1aXJlZF9lZHVjYXRpb24NCjEyLiBpbmR1c3RyeQ0KMTMuIGZ1bmN0aW9uDQoNCiMgTnVtZXJpY2FsIHZhcmlhYmxlcyBhcmU6DQoxLiBqb2JfaWQNCjIuIHRlbGVjb21tdXRpbmcNCjMuIGhhc19jb21wYW55X2xvZ28NCjQuIGhhc19xdWVzdGlvbnMNCg0KIyBUaGUgbGFzdCB2YXJpYWJsZSAtIGZyYXVkdWxlbnQgaXMgYSBudW1lcmljYWwgYmluYXJ5IHZhcmlhYmxlIHRvIGJlIHByZWRpY3RlZC4NCg0KYGBge3J9DQojIExldCdzIGNoZWNrIGZvciBudWxsIHZhbHVlcw0KY29sU3Vtcyhpcy5uYShkYXRhKSkNCmBgYA0KSGVyZSwgV2UgY2FuIHNlZSB0aGF0IG1hbnkgY29sdW1ucyBoYXZlIG51bGwgdmFsdWVzLCBsZXQncyBoYW5kbGUgdGhlbToNCg0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRpbmcgdGhlIGNvcnJlbGF0aW9uIGZvcjogZGVwYXJ0bWVudCwgc2FsYXJ5X3JhbmdlLGZ1bmN0aW9uLg0KIyNkZXBhcnRtZW50DQojVHJhaW4gRGF0YQ0KdHJhaW5fZGF0YSRkZXBhcnRtZW50ID0gYXMubnVtZXJpYyhhcy5mYWN0b3IodHJhaW5fZGF0YSRkZXBhcnRtZW50KSkNCnRyYWluX2RhdGEkZGVwYXJ0bWVudCA9IGlmZWxzZShpcy5uYSh0cmFpbl9kYXRhJGRlcGFydG1lbnQpLCAwLCB0cmFpbl9kYXRhJGRlcGFydG1lbnQpDQpjb3JyZWxhdGlvbl9tYXRyaXggPSBjb3IodHJhaW5fZGF0YSRkZXBhcnRtZW50LCB0cmFpbl9kYXRhJGZyYXVkdWxlbnQpDQpjb3JyZWxhdGlvbl9tYXRyaXggIyAtMC4wNzc2ODM2NQ0KDQojVGVzdF9kYXRhDQp0ZXN0X2RhdGEkZGVwYXJ0bWVudCA9IGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlc3RfZGF0YSRkZXBhcnRtZW50KSkNCnRlc3RfZGF0YSRkZXBhcnRtZW50ID0gaWZlbHNlKGlzLm5hKHRlc3RfZGF0YSRkZXBhcnRtZW50KSwgMCwgdGVzdF9kYXRhJGRlcGFydG1lbnQpDQpjb3JyZWxhdGlvbl9tYXRyaXggPSBjb3IodGVzdF9kYXRhJGRlcGFydG1lbnQsIHRlc3RfZGF0YSRmcmF1ZHVsZW50KQ0KY29ycmVsYXRpb25fbWF0cml4ICMtMC4wMjk2MzI4Mg0KDQojI3NhbGFyeSBSYW5nZQ0KI1RyYWluIERhdGENCnRyYWluX2RhdGEkc2FsYXJ5X3JhbmdlID0gYXMubnVtZXJpYyhhcy5mYWN0b3IodHJhaW5fZGF0YSRzYWxhcnlfcmFuZ2UpKQ0KdHJhaW5fZGF0YSRzYWxhcnlfcmFuZ2UgPSBpZmVsc2UoaXMubmEodHJhaW5fZGF0YSRzYWxhcnlfcmFuZ2UpLCAwLCB0cmFpbl9kYXRhJHNhbGFyeV9yYW5nZSkNCmNvcnJlbGF0aW9uX21hdHJpeCA9IGNvcih0cmFpbl9kYXRhJHNhbGFyeV9yYW5nZSwgdHJhaW5fZGF0YSRmcmF1ZHVsZW50KQ0KY29ycmVsYXRpb25fbWF0cml4ICMwLjEwMTcyMjgNCg0KI1Rlc3QgRGF0YQ0KdGVzdF9kYXRhJHNhbGFyeV9yYW5nZSA9IGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlc3RfZGF0YSRzYWxhcnlfcmFuZ2UpKQ0KdGVzdF9kYXRhJHNhbGFyeV9yYW5nZSA9IGlmZWxzZShpcy5uYSh0ZXN0X2RhdGEkc2FsYXJ5X3JhbmdlKSwgMCwgdGVzdF9kYXRhJHNhbGFyeV9yYW5nZSkNCmNvcnJlbGF0aW9uX21hdHJpeCA9IGNvcih0ZXN0X2RhdGEkc2FsYXJ5X3JhbmdlLCB0ZXN0X2RhdGEkZnJhdWR1bGVudCkNCmNvcnJlbGF0aW9uX21hdHJpeCAjMC4xMjQ5ODYNCg0KIyNmdW5jdGlvbi4gDQojVHJhaW5fZGF0YQ0KdHJhaW5fZGF0YSRmdW5jdGlvbi4gPSBhcy5udW1lcmljKGFzLmZhY3Rvcih0cmFpbl9kYXRhJGZ1bmN0aW9uLikpDQp0cmFpbl9kYXRhJGZ1bmN0aW9uLiA9IGlmZWxzZShpcy5uYSh0cmFpbl9kYXRhJGZ1bmN0aW9uLiksIDAsIHRyYWluX2RhdGEkZnVuY3Rpb24uKQ0KY29ycmVsYXRpb25fbWF0cml4ID0gY29yKHRyYWluX2RhdGEkZnVuY3Rpb24uLCB0cmFpbl9kYXRhJGZyYXVkdWxlbnQpDQpjb3JyZWxhdGlvbl9tYXRyaXggIy0wLjE0NzYyOTcNCg0KI1Rlc3QgRGF0YQ0KdGVzdF9kYXRhJGZ1bmN0aW9uLiA9IGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlc3RfZGF0YSRmdW5jdGlvbi4pKQ0KdGVzdF9kYXRhJGZ1bmN0aW9uLiA9IGlmZWxzZShpcy5uYSh0ZXN0X2RhdGEkZnVuY3Rpb24uKSwgMCwgdGVzdF9kYXRhJGZ1bmN0aW9uLikNCmNvcnJlbGF0aW9uX21hdHJpeCA9IGNvcih0ZXN0X2RhdGEkZnVuY3Rpb24uLCB0ZXN0X2RhdGEkZnJhdWR1bGVudCkNCmNvcnJlbGF0aW9uX21hdHJpeCAjLTAuMTgwNTE2DQojIFRoZSBjb3JyZWxhdGlvbiB2YWx1ZSBpcyB2ZXJ5IGZhciBhd2F5IGZyb20gdGhlIHJhbmdlIC0xIHRvIDEsIHRoZXJlZm9yZSBpdCBzaG93cyB3ZWFrIHJlbGF0aW9uIGFuZCB3ZSdsbCByZW1vdmUgdGhlc2UgdmFyaWFibGVzIGZyb20gb3VyIGRhdGFzZXQuDQp0cmFpbl9kYXRhID0gc3Vic2V0KHRyYWluX2RhdGEsIHNlbGVjdCA9IC1jKHNhbGFyeV9yYW5nZSxkZXBhcnRtZW50LCBmdW5jdGlvbi4pKQ0KdGVzdF9kYXRhID0gc3Vic2V0KHRlc3RfZGF0YSwgc2VsZWN0ID0gLWMoc2FsYXJ5X3JhbmdlLGRlcGFydG1lbnQsIGZ1bmN0aW9uLikpDQpgYGANCkxldCdzIGltcHV0ZSB0aGUgbWlzc2luZyB2YWx1ZXM6DQoNCk5vIG51bWVyaWNhbCB2YXJpYWJsZXMgaGF2ZSBudWxsIHZhbHVlcy4NCg0KIyBMZXQncyBoYW5kbGUgdGhlIG1pc3NpbmcgdmFsdWVzIGJ5IGZpbGxpbmcgdGhlIE5BJ3Mgd2l0aCBhbiBlbXB0eSBzdHJpbmdzIGFzIHRoaXMgaXMgY29uc2lkZXJlZCBhcyB0aGUgYmVzdCB3YXkgdW50aWwgbm93IGJlY2F1c2Ugd2Ugd291bGQgYmUgY29tYmluaW5nIHRoZXNlIHZhcmlhYmxlcyBhbnlob3cgaW50byBvbmUgdmFyaWFibGUgYW5kIHRoZW4gd29yayB1cG9uIHRoZW0uDQoNCg0KDQoNCmBgYHtyfQ0KDQpkYXRhID0gZGF0YS5mcmFtZSgNCiAgbGFwcGx5KHRyYWluX2RhdGEsIGZ1bmN0aW9uKHgpIGlmZWxzZShpcy5uYSh4KSwgJycsIHgpKQ0KKQ0KY29sU3Vtcyhpcy5uYSh0cmFpbl9kYXRhKSkNCmBgYA0KDQojIExldCdzIHVuZGVyc3RhbmQgb3VyIG51bWVyaWNhbCB2YXJpYWJsZXM6DQoNCjEuIGpvYl9pZA0KMi4gdGVsZWNvbW11dGluZw0KMy4gaGFzX2NvbXBhbnlfbG9nbw0KNC4gaGFzX3F1ZXN0aW9ucw0KDQpgYGB7cn0NCg0KY291bnQgPSB0YWJsZSh0cmFpbl9kYXRhJHRlbGVjb21tdXRpbmcpDQpiYXJwbG90KGNvdW50LA0KICAgICAgICBtYWluID0gIkNvdW50IFBsb3QgZm9yIHRlbGVjb21tdXRpbmciLA0KICAgICAgICB4bGFiID0gImlkIiwNCiAgICAgICAgeWxhYiA9ICJDb3VudCIsDQogICAgICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgICAgYm9yZGVyID0gImJsYWNrIg0KKQ0KDQpjb3VudCA9IHRhYmxlKHRyYWluX2RhdGEkaGFzX2NvbXBhbnlfbG9nbykNCmJhcnBsb3QoY291bnQsDQogICAgICAgIG1haW4gPSAiQ291bnQgUGxvdCBmb3IgY29tcGFueV9sb2dvIiwNCiAgICAgICAgeGxhYiA9ICJpZCIsDQogICAgICAgIHlsYWIgPSAiQ291bnQiLA0KICAgICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgICAgIGJvcmRlciA9ICJibGFjayINCikNCg0KY291bnQgPSB0YWJsZSh0cmFpbl9kYXRhJGhhc19xdWVzdGlvbnMpDQpiYXJwbG90KGNvdW50LA0KICAgICAgICBtYWluID0gIkNvdW50IFBsb3QgZm9yIHF1ZXN0aW9ucyIsDQogICAgICAgIHhsYWIgPSAiaWQiLA0KICAgICAgICB5bGFiID0gIkNvdW50IiwNCiAgICAgICAgY29sID0gInNreWJsdWUiLA0KICAgICAgICBib3JkZXIgPSAiYmxhY2siDQopDQpgYGANCg0KVGhlcmUgaXMgaGlnaCBpbWJhbGFuY2Ugb2YgZGF0YSBmb3IgdGVsZWNvbW11dGluZyB2YXJpYWJsZS4gDQoNCiMgbGV0J3MgY2hlY2sgdGhlaXIgY29ycmVsYXRpb24gd2l0aCB0aGUgdGFyZ2V0IHZhcmlhYmxlOg0KDQpgYGB7cn0NCiMgRm9yIGpvYl9pZA0KY29ycmVsYXRpb24gPC0gY29yKHRyYWluX2RhdGEkam9iX2lkLCB0cmFpbl9kYXRhJGZyYXVkdWxlbnQpDQpjb3JyZWxhdGlvbiAjMC4xNTU3NjQ4DQoNCiMgRm9yIHRlbGVjb21tdXRpbmcNCmNvcnJlbGF0aW9uIDwtIGNvcih0cmFpbl9kYXRhJHRlbGVjb21tdXRpbmcsIHRyYWluX2RhdGEkZnJhdWR1bGVudCkNCmNvcnJlbGF0aW9uICMwLjA2OTk4MzAzDQoNCiMgRm9yIGhhc19jb21wYW55X2xvZ28NCmNvcnJlbGF0aW9uIDwtIGNvcih0cmFpbl9kYXRhJGhhc19jb21wYW55X2xvZ28sIHRyYWluX2RhdGEkZnJhdWR1bGVudCkNCmNvcnJlbGF0aW9uICMtMC40Nzg5MjUyDQoNCiMgRm9yIGhhc19xdWVzdGlvbnMNCmNvcnJlbGF0aW9uIDwtIGNvcih0cmFpbl9kYXRhJGhhc19xdWVzdGlvbnMsIHRyYWluX2RhdGEkZnJhdWR1bGVudCkNCmNvcnJlbGF0aW9uICMtMC4yMDU2MzE5DQoNCiMgUmVtb3ZpbmcgdGhlc2UgdGhyZWUgdmFyaWFibGUgZnJvbSB0aGUgZGF0YSBzZXQNCnRyYWluX2RhdGEgPSBzdWJzZXQodHJhaW5fZGF0YSwgc2VsZWN0ID0gLWMoam9iX2lkLHRlbGVjb21tdXRpbmcsaGFzX3F1ZXN0aW9ucykpDQp0ZXN0X2RhdGEgPSBzdWJzZXQodGVzdF9kYXRhLCBzZWxlY3QgPSAtYyhqb2JfaWQsdGVsZWNvbW11dGluZyxoYXNfcXVlc3Rpb25zKSkNCmBgYA0KIyBKb2JfaWQgY29ycmVsYXRpb24gdmFsdWUgaW5kaWNhdGVzIGEgd2VhayByZWxhdGlvbnNoaXAgYW5kIGhlbmNlLCB3ZSBjYW4gcmVtb3ZlIHRoaXMgYmVjYXVzZSBldmVuIGlmIHdlIGNyaXRpY2FsIHRoaW5rLCBqb2JfaWQgZG9lc24ndCBoZWxwIHVzIHRvIHVuZGVyc3RhbmQgaWYgdGhlIGpvYiBpcyBmcmF1ZHVsZW50IG9yIG5vdC4gSXQganVzdHMgYWN0IGFzIGEgdW5pcXVlIGlkZW50aWZpZXIgaW4gdGVybXMgb2YgZGF0YWJhc2UgdGVybS4NCg0KIyBUYWxraW5nIGFib3V0IHRlbGVjb21tdXRpbmcsIGhhc19xdWVzdGlvbnMgdmFyaWFibGUgd2Ugc2hvdWxkIHJlbW92ZSB0aGVtIGFzIHdlbGwuIEkgdGhpbmsgaXQncyBiZXR0ZXIgdG8ga2VlcCBoYXNfY29tcGFueV9sb2dvIHZhcmlhYmxlIGZvciBub3cgYXMgaXQgYXRsZWFzdCBnaXZlcyB1cyBhIG51bWJlciBjbG9zZSB0byAtMS4NCg0KDQpgYGB7cn0NCiMgTGV0J3MgY3JlYXRlIGEgZnVuY3Rpb24gdG8gcGVyZm9ybSB0aGUgY2hpLXNxdWFyZSB0ZXN0DQpjaGlfdGVzdCA9IGZ1bmN0aW9uKHZhciwgdGFyZ2V0KSB7DQogIGNoaV9zcSA9IGNoaXNxLnRlc3QodmFyLCB0YXJnZXQpDQogIHJldHVybihjaGlfc3EpDQp9DQpjYXRfdmFyID0gYygNCiAgICAidGl0bGUiLA0KICAgICJsb2NhdGlvbiIsDQogICAgImNvbXBhbnlfcHJvZmlsZSIsDQogICAgImRlc2NyaXB0aW9uIiwNCiAgICAicmVxdWlyZW1lbnRzIiwNCiAgICAiYmVuZWZpdHMiLA0KICAgICJlbXBsb3ltZW50X3R5cGUiLA0KICAgICJyZXF1aXJlZF9leHBlcmllbmNlIiwNCiAgICAicmVxdWlyZWRfZWR1Y2F0aW9uIiwNCiAgICAiaW5kdXN0cnkiDQopDQojIFBlcmZvcm0gY2hpLXNxdWFyZSB0ZXN0cyBmb3IgZWFjaCB2YXJpYWJsZSBhbmQgcHJpbnQgdGhlIHJlc3VsdHMNCmZvciAodmFyIGluIGNhdF92YXIpIHsNCiAgY2hpX3NxID0gY2hpX3Rlc3QodHJhaW5fZGF0YVtbdmFyXV0sIHRyYWluX2RhdGEkZnJhdWR1bGVudCkNCiAgdmFyDQogIHByaW50KGNoaV9zcSkNCn0NCmBgYA0KV2UgY2FuIHNlZSB0aGF0IHRoZSBwLXZhbHVlcyBhcmUgPCAwLjA1LCB0aGVyZWZvcmUgd2UnbGwgYWNjZXB0IHRoYXQgdGhlc2UgYXJlIHNpZ25pZmljYW50IHZhcmlhYmxlcy4NCg0KYGBge3J9DQpjaGFyX3ZhcnMgPSBzYXBwbHkodHJhaW5fZGF0YSwgaXMuY2hhcmFjdGVyKQ0KIyBDb252ZXJ0IHRob3NlIHZhcmlhYmxlcyB0byBmYWN0b3INCiNkYXRhW2NoYXJfdmFyc10gPSBsYXBwbHkoZGF0YVtjaGFyX3ZhcnNdLCBhcy5mYWN0b3IpDQpgYGANCg0KIyBMZXQncyB1bmRlcnN0YW5kIGEgZmV3IG1vcmUgdmFyaWFibGVzIHRocm91Z2ggcGxvdHRpbmcgdGhlIGdyYXBocyBmb3IgdGhlbToNCg0KYGBge3J9DQojdHJhaW5fZGF0YQ0KIyBQbG90dGluZyB0aGUgY291bnQgZm9yIHJlcXVpcmVkX2V4cGVyaWVuY2UgaW4gdGhlIGRhdGE6DQpleHBlcmllbmNlX2NvdW50cyA9IHRhYmxlKHRyYWluX2RhdGEkcmVxdWlyZWRfZXhwZXJpZW5jZSkNCnRvcF9leHBlcmllbmNlX2luZGV4ID0gaGVhZChvcmRlcihleHBlcmllbmNlX2NvdW50cywgZGVjcmVhc2luZyA9IFRSVUUpLCAxMCkNCnRvcF9jb3VudHMgPSBleHBlcmllbmNlX2NvdW50c1t0b3BfZXhwZXJpZW5jZV9pbmRleF0NCmJhcnBsb3QodG9wX2NvdW50cywNCiAgICAgICAgbWFpbiA9ICJDb3VudCBQbG90IGZvciBFeHBlcmllbmNlIiwNCiAgICAgICAgeGxhYiA9ICJFeHBlcmllbmNlIiwNCiAgICAgICAgeWxhYiA9ICJDb3VudCIsDQogICAgICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgICAgYm9yZGVyID0gImJsYWNrIiwNCiAgICAgICAgbGFzID0gMiAgDQopDQpgYGANCldlIGNhbiBkZWR1Y2UgdGhhdCBtb3N0IHZhbHVlcyBhcmUgbnVsbCBhbmQgdGhlbiB0aGUgaGlyaW5nIGlzIGhhcHBlbmluZyBtb3JlIGZvciBzZW5pb3IgbGV2ZWwgYW5kIGVudHJ5IGxldmVsIGFzIGNvbXBhcmVkIHRvIG90aGVyIHR5cGVzIG9mIGV4cGVyaWVuY2UgbmVlZGVkLg0KDQpgYGB7cn0NCiMgTGV0J3MgZXh0cmFjdCBjb3VudHJ5IGZyb20gdGhlIGxvY2F0aW9uIGFuZCBjaGVjayBpdCdzIGNvdW50DQp0cmFpbl9kYXRhJGNvdW50cnkgPC0gc3ViKCIsLioiLCAiIiwgdHJhaW5fZGF0YSRsb2NhdGlvbikNCnRlc3RfZGF0YSRjb3VudHJ5IDwtIHN1YigiLC4qIiwgIiIsIHRlc3RfZGF0YSRsb2NhdGlvbikNCg0KIyBQbG90dGluZyB0aGUgY291bnQgZm9yIDEwIGhpZ2hlc3QgY291bnRzIGNvdW50cmllcyBmcm9tIHRoZSBkYXRhOg0KY291bnRyeV9jb3VudHMgPSB0YWJsZSh0cmFpbl9kYXRhJGNvdW50cnkpDQp0b3BfY291bnRyaWVzX2luZGV4ID0gaGVhZChvcmRlcihjb3VudHJ5X2NvdW50cywgZGVjcmVhc2luZyA9IFRSVUUpLCAxMCkNCnRvcF9jb3VudHMgPSBjb3VudHJ5X2NvdW50c1t0b3BfY291bnRyaWVzX2luZGV4XQ0KYmFycGxvdCh0b3BfY291bnRzLA0KICAgICAgICBtYWluID0gIkNvdW50IFBsb3QgZm9yIENvdW50cmllcyIsDQogICAgICAgIHhsYWIgPSAiQ291bnRyaWVzIiwNCiAgICAgICAgeWxhYiA9ICJDb3VudCIsDQogICAgICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgICAgYm9yZGVyID0gImJsYWNrIiwNCiAgICAgICAgbGFzID0gMiAgDQopDQojdHJhaW5fZGF0YSRjb3VudHJ5ID0gYXMuZmFjdG9yKHRyYWluX2RhdGEkY291bnRyeSkNCmBgYA0KVVMgaGFzIHRoZSBtb3N0IG9wZW5pbmdzLg0KDQpgYGB7cn0NCiMgUGxvdHRpbmcgdGhlIGNvdW50IGZvciBoaWdoZXN0IGNvdW50cyBlZHVjYXRpb24gZnJvbSB0aGUgZGF0YToNCmVkX2NvdW50cyA9IHRhYmxlKHRyYWluX2RhdGEkcmVxdWlyZWRfZWR1Y2F0aW9uKQ0KZWRfaW5kZXggPSBoZWFkKG9yZGVyKGVkX2NvdW50cywgZGVjcmVhc2luZyA9IFRSVUUpLCA1KQ0KdG9wX2NvdW50cyA9IGVkX2NvdW50c1tlZF9pbmRleF0NCmJhcnBsb3QodG9wX2NvdW50cywNCiAgICAgICAgbWFpbiA9ICJDb3VudCBQbG90IGZvciBFZHVjYXRpb24gTGV2ZWwiLA0KICAgICAgICB4bGFiID0gIkVkdWNhdGlvbiIsDQogICAgICAgIHlsYWIgPSAiQ291bnQiLA0KICAgICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgICAgIGJvcmRlciA9ICJibGFjayIsDQogICAgICAgIGxhcyA9IDIgIA0KKQ0KYGBgDQpBZ2FpbiBmb3IgdGhpcywgQmFjaGVsb3IncyBkZWdyZWUgcmVxdWlyZW1lbnQgaXMgbW9yZS4NCg0KIyBMZXQncyB0cnkgdG8gZmluZCB0aGUgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgYW1vbmcgdGhlc2UgdGV4dCB2YXJpYWJsZXMgb2Ygb3VyIGRhdGFzZXQsIHRvIGNvbWJpbmUgdGhlbSANCg0KYGBge3J9DQpjaGlfdGVzdCA8LSBmdW5jdGlvbih2YXIsIHRhcmdldCkgew0KICBjaGlfc3EgPC0gY2hpc3EudGVzdCh2YXIsIHRhcmdldCkNCiAgcmV0dXJuKGNoaV9zcSRzdGF0aXN0aWMpDQp9DQoNCmNhdF92YXIgPC0gYygNCiAgInRpdGxlIiwNCiAgImNvdW50cnkiLA0KICAiY29tcGFueV9wcm9maWxlIiwNCiAgImRlc2NyaXB0aW9uIiwNCiAgInJlcXVpcmVtZW50cyIsDQogICJiZW5lZml0cyIsDQogICJlbXBsb3ltZW50X3R5cGUiLA0KICAicmVxdWlyZWRfZXhwZXJpZW5jZSIsDQogICJyZXF1aXJlZF9lZHVjYXRpb24iLA0KICAiaW5kdXN0cnkiLA0KICAiaGFzX2NvbXBhbnlfbG9nbyINCikNCg0KIyBJbml0aWFsaXppbmcgYW4gZW1wdHkgdmVjdG9yIHRvIHN0b3JlIGNoaS1zcXVhcmUgc3RhdGlzdGljcyBmb3IgZWFjaCB2YXJpYWJsZQ0KY2hpX3NxX3N0YXRzIDwtIG51bWVyaWMobGVuZ3RoKGNhdF92YXIpKQ0KDQojIFBlcmZvcm1pbmcgY2hpLXNxdWFyZSB0ZXN0cyBmb3IgZWFjaCB2YXJpYWJsZSBhbmQgc3RvcmluZyB0aGUgcmVzdWx0cw0KZm9yIChpIGluIHNlcV9hbG9uZyhjYXRfdmFyKSkgew0KICBjaGlfc3Ffc3RhdHNbaV0gPC0gY2hpX3Rlc3QodHJhaW5fZGF0YVtbY2F0X3ZhcltpXV1dLCB0cmFpbl9kYXRhJGZyYXVkdWxlbnQpDQp9DQoNCiMgTmV3IGRhdGEgZnJhbWUgd2l0aCB2YXJpYWJsZSBuYW1lcyBhbmQgdGhlaXIgY29ycmVzcG9uZGluZyBjaGktc3F1YXJlIHN0YXRpc3RpY3MgY3JlYXRlZA0KY2hpX3NxX2RmIDwtIGRhdGEuZnJhbWUodmFyaWFibGUgPSBjYXRfdmFyLCBjaGlfc3Ffc3RhdGlzdGljID0gY2hpX3NxX3N0YXRzKQ0KDQojIE9yZGVyaW5nIGJhc2VkIG9uIGNoaS1zcXVhcmUgc3RhdGlzdGljcyBpbiBkZXNjZW5kaW5nIG9yZGVyDQpjaGlfc3FfZGYgPC0gY2hpX3NxX2RmW29yZGVyKGNoaV9zcV9kZiRjaGlfc3Ffc3RhdGlzdGljLCBkZWNyZWFzaW5nID0gVFJVRSksIF0NCmNoaV9zcV9kZg0KYGBgDQpXZSBjYW4gZGlzY2FyZCBjb3VudHJ5LCBsb2NhdGlvbiwgcmVxdWlyZWRfZWR1Y2F0aW9uICxyZXF1aXJlZF9leHBlcmllbmNlICxlbXBsb3ltZW50X3R5cGUsIGxvY2F0aW9uIGZyb20gdGhlIGRhdGFzZXQuDQoNCmBgYHtyfQ0KI3RyYWluX2RhdGENCnRyYWluX2RhdGEgPSBzdWJzZXQodHJhaW5fZGF0YSwgc2VsZWN0ID0gLWMoY291bnRyeSwgbG9jYXRpb24sIHJlcXVpcmVkX2VkdWNhdGlvbiAscmVxdWlyZWRfZXhwZXJpZW5jZSAsZW1wbG95bWVudF90eXBlLCBsb2NhdGlvbixoYXNfY29tcGFueV9sb2dvICkpDQp0cmFpbl9kYXRhDQojdGVzdF9kYXRhDQojdHJhaW5fZGF0YQ0KdGVzdF9kYXRhID0gc3Vic2V0KHRlc3RfZGF0YSwgc2VsZWN0ID0gLWMoY291bnRyeSwgbG9jYXRpb24sIHJlcXVpcmVkX2VkdWNhdGlvbiAscmVxdWlyZWRfZXhwZXJpZW5jZSAsZW1wbG95bWVudF90eXBlLCBsb2NhdGlvbixoYXNfY29tcGFueV9sb2dvICkpDQp0ZXN0X2RhdGENCmBgYA0KDQojIE5vdywgbGV0J3MgY29tYmluZSBhbGwgb3VyIHZhcmlhYmxlczogdGl0bGUsIGNvbXBhbnlfcHJvZmlsZSwgZGVzY3JpcHRpb24sIHJlcXVpcmVtZW50cywgYmVuZWZpdHMsIGluZHVzdHJ5Og0KDQpgYGB7cn0NCnRyYWluX2RhdGEkdGV4dCA8LSBwYXN0ZSh0cmFpbl9kYXRhJHRpdGxlLCB0cmFpbl9kYXRhJGNvbXBhbnlfcHJvZmlsZSwgdHJhaW5fZGF0YSRkZXNjcmlwdGlvbiwgdHJhaW5fZGF0YSRyZXF1aXJlbWVudHMsIHRyYWluX2RhdGEkYmVuZWZpdHMsIHRyYWluX2RhdGEkaW5kdXN0cnksIHNlcCA9ICIgIikNCg0KdGVzdF9kYXRhJHRleHQgPC0gcGFzdGUodGVzdF9kYXRhJHRpdGxlLCB0ZXN0X2RhdGEkY29tcGFueV9wcm9maWxlLCB0ZXN0X2RhdGEkZGVzY3JpcHRpb24sIHRlc3RfZGF0YSRyZXF1aXJlbWVudHMsIHRlc3RfZGF0YSRiZW5lZml0cywgdGVzdF9kYXRhJGluZHVzdHJ5LCBzZXAgPSAiICIpDQoNCiMgUmVtb3ZpbmcgdGhlc2UgdmFyaWFibGVzOg0KI3RyYWluX2RhdGENCnRyYWluX2RhdGEgPSBzdWJzZXQodHJhaW5fZGF0YSwgc2VsZWN0ID0gLWModGl0bGUsIGNvbXBhbnlfcHJvZmlsZSwgZGVzY3JpcHRpb24sIHJlcXVpcmVtZW50cyxiZW5lZml0cyxpbmR1c3RyeSkpDQoNCiN0ZXN0X2RhdGENCnRlc3RfZGF0YSA9IHN1YnNldCh0ZXN0X2RhdGEsIHNlbGVjdCA9IC1jKHRpdGxlLCBjb21wYW55X3Byb2ZpbGUsIGRlc2NyaXB0aW9uLCByZXF1aXJlbWVudHMsYmVuZWZpdHMsaW5kdXN0cnkpKQ0KYGBgDQojIFJhbmRvbWl6aW5nIHRoZSBvcmRlcjoNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQp0cmFpbl9kYXRhID0gdHJhaW5fZGF0YVtzYW1wbGUobnJvdyh0cmFpbl9kYXRhKSksIF0NCnRlc3RfZGF0YSA9IHRlc3RfZGF0YVtzYW1wbGUobnJvdyh0ZXN0X2RhdGEpKSwgXQ0KDQpgYGANCiMgTGV0J3MgcGVyZm9ybSBzb21lIHByZS1wcm9jZXNzaW5nDQoNCmBgYHtyfQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShTbm93YmFsbEMpDQp0ZXh0X2NvcnB1c190cmFpbiA9IFZDb3JwdXMoVmVjdG9yU291cmNlKHRyYWluX2RhdGEkdGV4dCkpDQp0ZXh0X2NvcnB1c190ZXN0ID0gVkNvcnB1cyhWZWN0b3JTb3VyY2UodGVzdF9kYXRhJHRleHQpKQ0KYGBgDQoNCiMgQ2xlYW5pbmcgdGhlIGNvcnB1cw0KYGBge3J9DQojdHJhaW5fZGF0YQ0KdGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4gPC0gdG1fbWFwKHRleHRfY29ycHVzX3RyYWluLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKSAjIENvbnZlcnQgdG8gbG93ZXJjYXNlDQp0ZXh0X2NvcnB1c19jbGVhbl90cmFpbiA8LSB0bV9tYXAodGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4sIHJlbW92ZU51bWJlcnMpICMgUmVtb3ZlIG51bWJlcnMNCnRleHRfY29ycHVzX2NsZWFuX3RyYWluIDwtIHRtX21hcCh0ZXh0X2NvcnB1c19jbGVhbl90cmFpbixyZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCkpICMgUmVtb3ZlIHN0b3Agd29yZHMNCnRleHRfY29ycHVzX2NsZWFuX3RyYWluIDwtIHRtX21hcCh0ZXh0X2NvcnB1c19jbGVhbl90cmFpbiwgcmVtb3ZlUHVuY3R1YXRpb24pICMgUmVtb3ZlIHB1bmN0dWF0aW9uDQp0ZXh0X2NvcnB1c19jbGVhbl90cmFpbiA8LSB0bV9tYXAodGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4sIHN0ZW1Eb2N1bWVudCkgIyBTdGVtbWluZw0KdGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4gPC0gdG1fbWFwKHRleHRfY29ycHVzX2NsZWFuX3RyYWluLCBzdHJpcFdoaXRlc3BhY2UpICMgUmVtb3ZlIGV4dHJhIHdoaXRlc3BhY2VzDQoNCiN0ZXN0X2RhdGENCnRleHRfY29ycHVzX2NsZWFuX3Rlc3QgPC0gdG1fbWFwKHRleHRfY29ycHVzX3Rlc3QsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgQ29udmVydCB0byBsb3dlcmNhc2UNCnRleHRfY29ycHVzX2NsZWFuX3Rlc3QgPC0gdG1fbWFwKHRleHRfY29ycHVzX2NsZWFuX3Rlc3QsIHJlbW92ZU51bWJlcnMpICMgUmVtb3ZlIG51bWJlcnMNCnRleHRfY29ycHVzX2NsZWFuX3Rlc3QgPC0gdG1fbWFwKHRleHRfY29ycHVzX2NsZWFuX3Rlc3QscmVtb3ZlV29yZHMsIHN0b3B3b3JkcygpKSAjIFJlbW92ZSBzdG9wIHdvcmRzDQp0ZXh0X2NvcnB1c19jbGVhbl90ZXN0IDwtIHRtX21hcCh0ZXh0X2NvcnB1c19jbGVhbl90ZXN0LCByZW1vdmVQdW5jdHVhdGlvbikgIyBSZW1vdmUgcHVuY3R1YXRpb24NCnRleHRfY29ycHVzX2NsZWFuX3Rlc3QgPC0gdG1fbWFwKHRleHRfY29ycHVzX2NsZWFuX3Rlc3QsIHN0ZW1Eb2N1bWVudCkgIyBTdGVtbWluZw0KdGV4dF9jb3JwdXNfY2xlYW5fdGVzdCA8LSB0bV9tYXAodGV4dF9jb3JwdXNfY2xlYW5fdGVzdCwgc3RyaXBXaGl0ZXNwYWNlKSAjIFJlbW92ZSBleHRyYSB3aGl0ZXNwYWNlcw0KYGBgDQoNCiMgQ3JlYXRpbmcgV29yZGNsb3VkczoNCmBgYHtyfQ0KI3RyYWluX2RhdGENCndvcmRjbG91ZCh0ZXh0X2NvcnB1c19jbGVhbl90cmFpbixtaW4uZnJlcSA9IDUwLCByYW5kb20ub3JkZXIgPSBGQUxTRSkNCmZyYXVkdWxlbnQgPSBzdWJzZXQodGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4sIHRyYWluX2RhdGEkZnJhdWR1bGVudCA9PSAxKQ0Kbm9uX2ZyYXVkdWxlbnQgPSBzdWJzZXQodGV4dF9jb3JwdXNfY2xlYW5fdHJhaW4sIHRyYWluX2RhdGEkZnJhdWR1bGVudCA9PSAwKQ0Kd29yZGNsb3VkKGZyYXVkdWxlbnQsIG1heC53b3JkcyA9IDIwLCBzY2FsZSA9IGMoMywgMC41KSkNCndvcmRjbG91ZChub25fZnJhdWR1bGVudCwgbWF4LndvcmRzID0gMjAsIHNjYWxlID0gYygzLCAwLjUpKQ0KDQojdGVzdF9kYXRhDQp3b3JkY2xvdWQodGV4dF9jb3JwdXNfY2xlYW5fdGVzdCxtaW4uZnJlcSA9IDUwLCByYW5kb20ub3JkZXIgPSBGQUxTRSkNCmZyYXVkdWxlbnQgPSBzdWJzZXQodGV4dF9jb3JwdXNfY2xlYW5fdGVzdCwgdGVzdF9kYXRhJGZyYXVkdWxlbnQgPT0gMSkNCm5vbl9mcmF1ZHVsZW50ID0gc3Vic2V0KHRleHRfY29ycHVzX2NsZWFuX3RyYWluLCB0ZXN0X2RhdGEkZnJhdWR1bGVudCA9PSAwKQ0Kd29yZGNsb3VkKGZyYXVkdWxlbnQsIG1heC53b3JkcyA9IDIwLCBzY2FsZSA9IGMoMywgMC41KSkNCndvcmRjbG91ZChub25fZnJhdWR1bGVudCwgbWF4LndvcmRzID0gMjAsIHNjYWxlID0gYygzLCAwLjUpKQ0KYGBgDQoNCiMgQ3JlYXRpbmcgdGhlIGRvY3VtZW50IHRlcm0gbWF0cml4OiANCmBgYHtyfQ0KI3RyYWluX2RhdGENCnRyYWluX2RhdGFfZHRtID0gRG9jdW1lbnRUZXJtTWF0cml4KHRleHRfY29ycHVzX2NsZWFuX3RyYWluKQ0KdHJhaW5fZGF0YV9kdG0gDQojdGVzdF9kYXRhDQp0ZXN0X2RhdGFfZHRtID0gRG9jdW1lbnRUZXJtTWF0cml4KHRleHRfY29ycHVzX2NsZWFuX3Rlc3QpDQp0ZXN0X2RhdGFfZHRtIA0KYGBgDQojIExldCdzIHBlcmZvcm0gdHJhaW4tdGVzdCBzcGxpdCwgd2UnbGwgYmUgdGFraW5nIDcwJSBkYXRhIGZvciB0cmFpbmluZyBhbmQgMzAlIGZvciB0ZXN0aW5nLg0KYGBge3J9DQppbmRleCA9IHJvdW5kKG5yb3codHJhaW5fZGF0YV9kdG0pKjAuNzApDQp0cmFpbl9kdG0gPSB0cmFpbl9kYXRhX2R0bVsxOmluZGV4LF0NCnRlc3RfZHRtID0gdHJhaW5fZGF0YV9kdG1bKGluZGV4KzEpOm5yb3codHJhaW5fZGF0YV9kdG0pLF0NCg0KdHJhaW5fbGFiZWwgPSB0cmFpbl9kYXRhWzE6aW5kZXgsXSRmcmF1ZHVsZW50DQp0ZXN0X2xhYmVsID0gdHJhaW5fZGF0YVsoaW5kZXgrMSk6bnJvdyh0cmFpbl9kYXRhKSxdJGZyYXVkdWxlbnQNCg0KI2NoZWNraW5nIHRoZSBsZW5ndGguIA0KbnJvdyh0cmFpbl9kdG0pDQpsZW5ndGgodHJhaW5fbGFiZWwpDQpucm93KHRlc3RfZHRtKQ0KbGVuZ3RoKHRlc3RfbGFiZWwpDQpgYGANCmBgYHtyfQ0KI3NpbXBsZSBiZW5jaG1hcmsgbW9kZWwNCg0KI3N1bSh0ZXN0X2RhdGEkZnJhdWR1bGVudCA9PSAxKQ0KbGVuZ3RoKHRlc3RfZGF0YSRmcmF1ZHVsZW50KQ0KdmVjdG9yIDwtIHJlcCgxICwgMjg2KQ0KcHJlZF90YWJsZSA8LSB0YWJsZSh2ZWN0b3IgLCB0ZXN0X2RhdGEkZnJhdWR1bGVudCkNCnByZWRfdGFibGUgDQp0b3RhbCA8LSAxOTMgKyA5Mw0KVFA8LTANClROPC0xOTMgIA0KRlA8LTANCkZOPC05Mw0KYWNjdXJhY3kgPC0gKFRQICsgVE4pIC8gdG90YWwNCmFjY3VyYWN5ICMwLjY3NDgyNTINCmBgYA0KIyBDcmVhdGluZyBmcmVxdWVuY3kgdGVybXMgdXNpbmcgb3VyIGRvY3VtZW50IHRlcm0gbWF0cml4IGRhdGENCmBgYHtyfQ0KI3RyYWluX2RhdGENCnRleHRGcmVxV29yZHNfdHJhaW4gPSBmaW5kRnJlcVRlcm1zKHRyYWluX2R0bSwxMCkNCnRyYWluX2R0bV9mcmVxID0gdHJhaW5fZHRtWyx0ZXh0RnJlcVdvcmRzX3RyYWluXQ0KdHJhaW5fZHRtX2ZyZXENCg0KI1Rlc3RfZGF0YQ0KdGV4dEZyZXFXb3Jkc190ZXN0ID0gZmluZEZyZXFUZXJtcyh0ZXN0X2R0bSwxMCkNCnRlc3RfZHRtX2ZyZXEgPSB0ZXN0X2R0bVssdGV4dEZyZXFXb3Jkc190ZXN0XQ0KdGVzdF9kdG1fZnJlcQ0KDQpjb252ZXJ0Q291bnRzID0gZnVuY3Rpb24oeCkgew0KICB4ID0gaWZlbHNlKHggPiAwLCAiWWVzIiwgIk5vIikNCn0NCg0KdHJhaW5fdGV4dCA9IGFwcGx5KHRyYWluX2R0bV9mcmVxLCBNQVJHSU4gPSAyLCBjb252ZXJ0Q291bnRzKQ0KdGVzdF90ZXh0ID0gYXBwbHkodGVzdF9kdG1fZnJlcSwgTUFSR0lOID0gMiwgY29udmVydENvdW50cykNCg0KYGBgDQojIFRoZSBNb2RlbHMgd2UnbGwgYmUgdXNpbmcgaGVyZSBhcmU6DQojIDEuIE5haXZlIEJheWVzDQojIDIuIExvZ2lzdGljIFJlZ3Jlc3Npb24gKCB0YWtpbmcgdG9vIGxvbmcgKQ0KIyA0LiBEZWNpc2lvbiBUcmVlcw0KIyA1LiBHcmFkaWVudCBCb29zdGluZyBNYWNoaW5lDQojIDYuIFJhbmRvbSBGb3Jlc3QNCiMgNy4gUk5OICggTFNUTSApDQoNCg0KIyBMZXQncyBzdGFydCB3aXRoIE5haXZlIEJheWVzOg0KDQpgYGB7cn0NCmxpYnJhcnkoZTEwNzEpDQpsaWJyYXJ5KGdtb2RlbHMpDQoNCnRlc3RDbGFzc2lmaWVyID0gbmFpdmVCYXllcyh0cmFpbl90ZXh0LHRyYWluX2xhYmVsKQ0KbmFpdmVfcHJlZGljdGlvbiA9IHByZWRpY3QodGVzdENsYXNzaWZpZXIsdGVzdF90ZXh0KQ0KDQpjb25mdXNpb25fbWF0cml4IDwtIENyb3NzVGFibGUobmFpdmVfcHJlZGljdGlvbix0ZXN0X2xhYmVsLCBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLGRubiA9IGMoJ3ByZWRpY3RlZCcsICdhY3R1YWwnKSkNCg0KI2NhbGN1bGF0aW9ucyBmb3IgaW52ZXJzZSBvZiBjb25mdXNpb24gbWF0cml4IHRvIGZpbmQgb3V0IHRoZSBmcmF1ZCBjYXNlcy4gDQpjb25mdXNpb25fbWF0cml4DQp0b3RhbCA8LSAyMTArNDYyKzc1KzI3DQpUUDwtMjEwDQpUTjwtNDYyDQpGUDwtNzUNCkZOPC0yNw0KDQphY2N1cmFjeSA8LSAoVFAgKyBUTikgLyB0b3RhbA0KYWNjdXJhY3kgIzAuODY4MjE3MQ0KcmVjYWxsIDwtIFRQLyhUUCtGTikNCnJlY2FsbCAjIDAuODg2MDc1OQ0KcHJlY2lzaW9uPC0gVFAvKFRQK0ZQKQ0KcHJlY2lzaW9uICMwLjczNjg0MjENCkYxX3Njb3JlIDwtIDIqKChwcmVjaXNpb24qcmVjYWxsKS8ocHJlY2lzaW9uK3JlY2FsbCkpDQpGMV9zY29yZSAjMC44MDQ1OTc3DQpgYGANCmBgYHtyfQ0KdHJhaW5fZGF0YV9kZiA8LSBhcy5kYXRhLmZyYW1lKHRyYWluX3RleHQpDQp0ZXN0X2RhdGFfZGYgPC0gYXMuZGF0YS5mcmFtZSh0ZXN0X3RleHQpDQoNCg0Kc3RyKHRyYWluX2xhYmVsKQ0KIDwtIGFzLmZhY3Rvcih0ZXh0VHJhaW5MYWJlbHMpDQp0ZXh0VGVzdExhYmVscyA8LSBhcy5mYWN0b3IodGV4dFRlc3RMYWJlbHMpDQpgYGANCg0KDQojIERlY2lzaW9uIFRyZWVzDQoNCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCnRyZWVfbW9kZWwgPC0gcnBhcnQoIHRyYWluX2xhYmVsfiAuLCBkYXRhID0gdHJhaW5fZGF0YV9kZiwgbWV0aG9kID0gImNsYXNzIikNCnByZWRpY3Rpb25zIDwtIHByZWRpY3QodHJlZV9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YV9kZiwgdHlwZSA9ICJjbGFzcyIpDQpjb25mdXNpb25fbWF0cml4IDwtIHRhYmxlKHByZWRpY3Rpb25zLCB0ZXN0X2xhYmVsKQ0KY29uZnVzaW9uX21hdHJpeA0KYGBgDQojIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmUNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoZ2JtKQ0KdHJhaW5fY29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSA1KQ0KZ2JtX21vZGVsIDwtIHRyYWluKGFzLmZvcm11bGEodGV4dFRyYWluTGFiZWxzIH4gLiwpLCANCiAgICAgICAgICAgICAgICAgICBkYXRhID0gdGV4dFRyYWluX2RmLCANCiAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2JtIiwNCiAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA1KSAgIyBOdW1iZXIgb2YgbW9kZWxzIHRvIGV2YWx1YXRlDQoNCiMgUHJpbnQgdGhlIHRyYWluZWQgbW9kZWwNCnByaW50KGdibV9tb2RlbCkNCg0KIyBwcmVkaWN0aW5nIG91ciBtb2RlbA0KZ2JtX3ByZWRpY3Rpb24gPSBwcmVkaWN0KGdibV9tb2RlbCwgdGV4dFRlc3RfZGYpDQoNCiMgQ3JlYXRpbmcgYSBjb25mdXNpb24gbWF0cml4DQp0YWJsZShnYm1fcHJlZGljdGlvbiwgdGV4dFRlc3RfZGYpDQpgYGANCiMgUmFuZG9tIEZvcmVzdA0KDQpgYGB7ciwgY2FjaGU9VFJVRX0NCnNldC5zZWVkKDEpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShnYm0pDQp0cmFpbl9jb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDMpDQpyZl9tb2RlbCA8LSB0cmFpbihhcy5mb3JtdWxhKHRleHRUcmFpbkxhYmVscyB+IC4sKSwgDQogICAgICAgICAgICAgICAgICBkYXRhID0gdGV4dFRyYWluX2RmLCANCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsDQogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDMsICANCiAgICAgICAgICAgICAgICAgIGltcG9ydGFuY2UgPSBUUlVFKQ0KDQpwcmludChyZl9tb2RlbCkNCg0KIyBwcmVkaWN0aW5nIG91ciBtb2RlbA0KcmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9uID0gcHJlZGljdChyZl9tb2RlbCwgdGV4dFRlc3RfZGYpDQoNCiMgQ3JlYXRpbmcgYSBjb25mdXNpb24gbWF0cml4DQp0YWJsZShyYW5kb21fZm9yZXN0X3ByZWRpY3Rpb24sIHRleHRUZXN0X2RmKQ0KYGBgDQoNCg0KYGBge3J9DQojIGhhbmRsZSBpbWJhbGFuY2UgZGF0YXNldA0KIyBBZGQgYmVuY2htYXJrIG1vZGVsDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0K